package registnet.base.student;

import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;

import org.apache.torque.TorqueException;

import registnet.base.Course;
import registnet.base.CourseTaken;
import registnet.base.ICourse;
import registnet.base.Priority;
import registnet.base.Standing;
import registnet.base.courseoffering.CourseOffering;
import registnet.base.source.CourseOfferingSource;
import registnet.dao.StudentStandingPeer;
import registnet.util.schedule.Day;
import registnet.util.schedule.Schedule;
import registnet.util.schedule.Task;
import resources.Constants;

/**
 * An implementation of a student.
 */
@SuppressWarnings("unused")
public class Student extends AbstractStudent {
	
	/**
	 * The current academic year.
	 */
	private int year = new registnet.dao.SystemPropertiesPeer().getSystemProperties().getAcademicYear();
	
	/**
	 * The current semester.
	 */
	private int semester = new registnet.dao.SystemPropertiesPeer().getSystemProperties().getSemester();
	
	/**
	 * The list of recommended courses.
	 */
	private List<Course> recommendedCourses;
	
	/**
	 * The list of course offerings that are not conflicting with the students schedule.
	 */
	private List<CourseOffering> options;
	
	/**
	 * The list of course offerings where the student is enlisted.
	 */
	private List<CourseOffering> enlistedCourseOfferings;
	
	/**
	 * The schedule of the student.
	 */
	private Schedule schedule;

	/**
	 * Default constructor.
	 */
	public Student(){ }
	
	/**
	 * Creates a student with the given student number.
	 * @param studentNumber
	 */
	public Student(String studentNumber){
		this.studentNumber = studentNumber;
		recommendedCourses = new Vector<Course>();
		enlistedCourseOfferings = new Vector<CourseOffering>();
		options = new Vector<CourseOffering>();
		schedule = new Schedule();
	}
	
	/**
	 * Compares two course offering according to their rank.
	 * 
	 * @param courseOffering0
	 * @param courseOffering1
	 * @return 1 if <code>courseOffering0</code>'s rank is greater than
	 * <code>courseOffering1</code>'s rank; 0 if the rank of both course
	 * offerings are equal; and -1 if <code>courseOffering0</code>'s rank 
	 * is less than <code>courseOffering1</code>'s rank
	 */
	private int compareOptionsByRank(CourseOffering courseOffering0, CourseOffering courseOffering1){
		if(courseOffering0.getRank() > courseOffering1.getRank())
			return 1;
		else if(courseOffering0.getRank() == courseOffering1.getRank())
			return 0;
		else
			return -1;
	}

	/**
	 * Update the student standing database.
	 */
	private void updateStandingInDatabase(){
		try{
			new registnet.dao.StudentStandingPeer().createStudentStanding(new registnet.dao.StudentPeer().getStudentId(studentNumber), Constants.MAP_STANDING_VALUE.get(standing.getDescription()),semester,year);
		}catch(Exception e){
			System.err.println("****[error] Student standing database update failed. "+e.getMessage());
		}
	}
	
	/**
	 * Update the student standing database for the given year and semester
	 * @param year
	 * @param semester
	 */
	private void updateStandingInDatabase(int year, int semester){
		try{
			new registnet.dao.StudentStandingPeer().createStudentStanding(new registnet.dao.StudentPeer().getStudentId(studentNumber), Constants.MAP_STANDING_VALUE.get(standing.getDescription()),semester,year);
		}catch(Exception e){
			System.err.println("****[error] Student standing database update failed. "+e.getMessage());
		}
	}
	
	/**
	 * Update the student classification database.
	 */
	private void updateClassificationInDatabase(){
		try{
			new registnet.dao.StudentClassificationPeer().createStudentClassification(new registnet.dao.StudentPeer().getStudentId(studentNumber), Constants.MAP_CLASSIFICATION_VALUE.get(classification.getDescription()), null, semester, year);
		}catch(Exception e){
			System.err.println("****[error] Student classification database update failed. "+e.getMessage());
		}
	}
	
	/**	
	 * Update the student priority database.
	 */
	private void updatePriorityInDatabase(){
		try{
			new registnet.dao.StudentPriorityPeer().createNewStudentPriority(new registnet.dao.StudentPeer().getStudentId(studentNumber), priority.getDescription());
		}catch(Exception e){
			new registnet.dao.StudentPriorityPeer().updateStudentPriority(new registnet.dao.StudentPeer().getStudentId(studentNumber), priority.getDescription());
			System.err.println("****[error] Student priority database update failed. "+e.getMessage());
		}	
	}
	
	/**
	 * Determines the student's standing (i.e. good standing, warning, dismissed, etc.).
	 */
	public void computeStanding(){
		List<ICourse> coursesTaken;
		int totalUnits = 0;
		int failedUnits = 0;
		
		Double percentage = 0d;
		try{
			coursesTaken = checklist.getTranscript().getCoursesTaken(year, semester);
		}catch(Exception e){ 
			System.err.println("****[error] Student("+studentNumber+") standing computation failed. "+e.getMessage());
			return; 
		}
		
		ListIterator<ICourse> li = coursesTaken.listIterator();
		while(li.hasNext()){
			CourseTaken course = (CourseTaken)li.next();
			if(course.getCourseCode().contains("PE ") || course.getCourseCode().contains("LTS ") || course.getCourseCode().contains("CWTS ") || course.getCourseCode().contains("ROTC "))
				continue;
			if(course.getGrade() != resources.Constants.MAP_GRADE_KEY.get("INC.") && course.getGrade() != resources.Constants.MAP_GRADE_KEY.get("DRP.")){	//grade of inc. is not included in the computation
				totalUnits += course.getUnit();
				if(course.getGrade() == resources.Constants.MAP_GRADE_KEY.get("REGD")){
					System.err.println("****[error] Student("+studentNumber+") has no grades for the current semester. Student standing computation failed.");
					return;
				}else if(course.getGrade()>3 && course.getGrade()!=resources.Constants.MAP_GRADE_KEY.get("PASS") && course.getGrade()!=resources.Constants.MAP_GRADE_KEY.get("S") && course.getReExam()!=3.0){
					failedUnits += course.getUnit();
				}
			}
		}
		if(totalUnits == 0)
			percentage = 0d;
		else
			percentage = ((double)failedUnits)/((double)totalUnits);
		
		/*
		 * Permanent Disqualification
		 */
		if(percentage == 1){
			standing = Standing.PERMANENTLY_DISQUALIFIED;
			updateStandingInDatabase();
			return;
		}
		
		/*
		 * Dismissal
		 */
		if(percentage > 0.75 && percentage < 1){	
			StudentStandingPeer daoStudentStandingPeer = new StudentStandingPeer();
			int previousStanding;
			registnet.dao.Student daoStudent = new registnet.dao.StudentPeer().getStudent(studentNumber);
			standing = Standing.DISMISSED;
			try{
				previousStanding = daoStudentStandingPeer.getLatestStudentStanding(daoStudent.getStudentId()).getStanding();
			}catch(Exception e){ e.printStackTrace(); return; }
			if(previousStanding == Constants.MAP_STANDING_VALUE.get(Standing.DISMISSED.getDescription()))
				standing = Standing.PERMANENTLY_DISQUALIFIED;
			updateStandingInDatabase();
			return;
		}
		
		/*
		 * Probation
		 */
		if(percentage >= 0.5 && percentage <= 0.75){ 
			StudentStandingPeer daoStudentStandingPeer = new StudentStandingPeer();
			int previousStanding;
			registnet.dao.Student daoStudent = new registnet.dao.StudentPeer().getStudent(studentNumber);
			standing = Standing.PROBATION;
			try{
				previousStanding = daoStudentStandingPeer.getLatestStudentStanding(daoStudent.getStudentId()).getStanding();
			}catch(Exception e){ e.printStackTrace(); return; }
			if(previousStanding == Constants.MAP_STANDING_VALUE.get(Standing.PROBATION.getDescription()))
				standing = Standing.DISMISSED;
			updateStandingInDatabase();
			return;
		}
		
		/*
		 * Warning
		 */
		if(percentage >= 0.25 && percentage < 50){
			standing = Standing.WARNING;
			updateStandingInDatabase();
			return;
		}
		
		/*
		 * Good Standing
		 */
		standing = Standing.GOOD_STANDING;
		updateStandingInDatabase();
	}
	
	/**
	 * Determines the student's standing (i.e. good standing, warning, dismissed, etc.) 
	 * for the given year and semester.
	 */
	public void computeStanding(int year, int semester){
		List<ICourse> coursesTaken;
		int totalUnits = 0;
		int failedUnits = 0;
		
		Double percentage = 0d;
		try{
			coursesTaken = checklist.getTranscript().getCoursesTaken(year, semester);
		}catch(Exception e){ 
			System.err.println("****[error] Student("+studentNumber+") standing computation failed. "+e.getMessage());
			return; 
		}
		
		ListIterator<ICourse> li = coursesTaken.listIterator();
		while(li.hasNext()){
			CourseTaken course = (CourseTaken)li.next();
			if(course.getCourseCode().contains("PE ") || course.getCourseCode().contains("LTS ") || course.getCourseCode().contains("CWTS ") || course.getCourseCode().contains("ROTC "))
				continue;
			if(course.getGrade() != resources.Constants.MAP_GRADE_KEY.get("INC.")  && course.getGrade() != resources.Constants.MAP_GRADE_KEY.get("DRP.")){	//grade of inc. is not included in the computation
				totalUnits += course.getUnit();
				if(course.getGrade() == resources.Constants.MAP_GRADE_KEY.get("REGD")){
					System.err.println("****[error] Student("+studentNumber+") has no grades for the current semester. Student standing computation failed.");
					return;
				}else if(course.getGrade()>3 && course.getGrade()!=resources.Constants.MAP_GRADE_KEY.get("PASS") && course.getGrade()!=resources.Constants.MAP_GRADE_KEY.get("S") && course.getGrade()!=resources.Constants.MAP_GRADE_KEY.get("DRP.") && course.getReExam()!=3.0){
					failedUnits += course.getUnit();
				}
			}
		}
		if(totalUnits == 0)
			percentage = 0d;
		else
			percentage = ((double)failedUnits)/((double)totalUnits);
		
		/*
		 * Permanent Disqualification
		 */
		if(percentage == 1){
			standing = Standing.PERMANENTLY_DISQUALIFIED;
			updateStandingInDatabase(year,semester);
			return;
		}
		
		/*
		 * Dismissal
		 */
		if(percentage > 0.75 && percentage < 1){	
			StudentStandingPeer daoStudentStandingPeer = new StudentStandingPeer();
			int previousStanding;
			registnet.dao.Student daoStudent = new registnet.dao.StudentPeer().getStudent(studentNumber);
			standing = Standing.DISMISSED;
			try{
				previousStanding = daoStudentStandingPeer.getLatestStudentStanding(daoStudent.getStudentId()).getStanding();
			}catch(Exception e){ e.printStackTrace(); return; }
			if(previousStanding == Constants.MAP_STANDING_VALUE.get(Standing.DISMISSED.getDescription()))
				standing = Standing.PERMANENTLY_DISQUALIFIED;
			updateStandingInDatabase(year,semester);
			return;
		}
		
		/*
		 * Probation
		 */
		if(percentage >= 0.5 && percentage <= 0.75){ 
			StudentStandingPeer daoStudentStandingPeer = new StudentStandingPeer();
			int previousStanding;
			registnet.dao.Student daoStudent = new registnet.dao.StudentPeer().getStudent(studentNumber);
			standing = Standing.PROBATION;
			try{
				previousStanding = daoStudentStandingPeer.getLatestStudentStanding(daoStudent.getStudentId()).getStanding();
			}catch(Exception e){ e.printStackTrace(); return; }
			if(previousStanding == Constants.MAP_STANDING_VALUE.get(Standing.PROBATION.getDescription()))
				standing = Standing.DISMISSED;
			updateStandingInDatabase(year,semester);
			return;
		}
		
		/*
		 * Warning
		 */
		if(percentage >= 0.25 && percentage < 50){
			standing = Standing.WARNING;
			updateStandingInDatabase(year,semester);
			return;
		}
		
		/*
		 * Good Standing
		 */
		standing = Standing.GOOD_STANDING;
		updateStandingInDatabase(year,semester);
	}
	
	
	/**
	 * Determines the student's classification (i.e. new freshman, old freshman, senior, etc.).
	 */
	public void computeClassification(){
		classification = checklist.getClassification();
		updateClassificationInDatabase();
	}

	
	/**
	 * Determines the student's priority for enlistment in courses (i.e. level 1, level 2, etc,).
	 */
	public void computePriority(){
		
		if(standing == null){
			return;
		}
		
		/*
		 * priority level 1
		 */
		if(getTranscript().getAllCoursesTaken().size() == 0){
			priority = Priority.PRIORITY_LEVEL_1;
			updatePriorityInDatabase();
			return;
		}
		
			
		if(standing.equals(Standing.GOOD_STANDING)){
			priority = Priority.PRIORITY_LEVEL_1;
			updatePriorityInDatabase();
			return;
		}
		
		/*
		 * priority level 2
		 */
		if(standing.equals(Standing.WARNING)){
			priority = Priority.PRIORITY_LEVEL_2;
			updatePriorityInDatabase();
			return;
		}
		
		
		/*
		 * priority level 3
		 */
		if(standing.equals(Standing.PROBATION)){
			priority = Priority.PRIORITY_LEVEL_3;
			updatePriorityInDatabase();
			return;
		}
		
		
		/*
		 * priority level 4
		 */
		if(standing.equals(Standing.DISMISSED) || standing.equals(Standing.PERMANENTLY_DISQUALIFIED)){
			priority = Priority.PRIORITY_LEVEL_4;
			updatePriorityInDatabase();
			return;
		}
	
		priority = null;
		System.err.println("****[error] Student("+studentNumber+") priority computation failed. ");
		return;
		
	}
	
	/**
	 * Returns the schedule of the student.
	 * @return the schedule of the student
	 */
	public Schedule getSchedule() {
		return schedule;
	}

	/**
	 * Sets the schedule of the student.
	 * @param schedule
	 */
	public void setSchedule(Schedule schedule) {
		this.schedule = schedule;
	}
	
	/**
	 * Adds a course to the list of recommended courses of the student.
	 * @param course
	 */
	public void addRecommendedCourse(Course course){
		if(course!=null)
			recommendedCourses.add(course);
	}
	
	/**
	 * Checks if the given course is in the list of recommended courses of the student.
	 * @param course
	 * @return
	 */
	public boolean hasRecommendedCourse(Course course){
		for(Course courseIte : recommendedCourses){
			if(course.getCourseCode().equals(courseIte.getCourseCode()))
				return true;
		}
		return false;
	}
	
	/**
	 * Adds a course offering to the list of options of the student.
	 * @param courseOffering
	 */
	public void addOption(CourseOffering courseOffering){
		options.add(courseOffering);
	}
	
	/**
	 * Computes the ranking of the options of the student.
	 * @param courseCode
	 */
	//implementation 2: number of course offerings of the other recommended courses
	//that will be conflicting with the student's schedule if the option is enlisted
	public void rankOptions(String courseCode){
		Integer semester = (new registnet.dao.SystemPropertiesPeer().getSystemProperties().getSemester() + 1)%3;
		Integer year = new registnet.dao.SystemPropertiesPeer().getSystemProperties().getAcademicYear();
		if(semester==3) year++;
		
		if(options.size()==0) return;
		
		for(CourseOffering option : options){
			double aveConflictCount = 0d;
			int recommendedCoursesCount = 0;
			
			for(Course recommendedCourse : recommendedCourses){
				double conflictCount = 0d;
				int courseOfferingCtr = 0;
				if(recommendedCourse.getCourseCode().equals(courseCode))
					continue;
				
				Schedule tempSchedule = this.getSchedule().combineSchedule(option.getSchedule());
				for(CourseOffering courseOffering : recommendedCourse.getCourseOfferings()){
					if(!courseOffering.getSchedule().hasTask())
						continue;
					courseOfferingCtr++;
					if(tempSchedule.isConflict(courseOffering.getSchedule()))
						conflictCount = conflictCount + 1d;
				}
				conflictCount = conflictCount / (double)courseOfferingCtr;
				recommendedCoursesCount++;
				aveConflictCount = aveConflictCount + conflictCount;
			}//end of foreach recommended course
			
			aveConflictCount = aveConflictCount / (double)recommendedCoursesCount;
			option.setRank((int)(aveConflictCount*100));
		}
		
	}
	
	/**
	 * Computes the ranking of the options of the student.
	 */
	//implementation 1: "distance" of the course offering's tasks from the student schedule's tasks 
	public void rankOptions(){
		ListIterator<CourseOffering> liOptions = this.options.listIterator();
		while(liOptions.hasNext()){
			CourseOffering option = liOptions.next();
			double aveDistance = 0;
			int ctr = 0;
			
			Iterator<String> iOptionDays = option.getSchedule().getDays().keySet().iterator();
			while(iOptionDays.hasNext()){
				String optionDay = iOptionDays.next();
				double totalDistance = 0;
				
				ListIterator<Task> liOptionTasks = option.getSchedule().getDays().get(optionDay).getTasks().listIterator();
				if(liOptionTasks.hasNext()) ctr++;
				while(liOptionTasks.hasNext()){
					Task optionTask = liOptionTasks.next();
					double minDistance = 500;
					
					ListIterator<Task> liStudentTasks = this.getSchedule().getDays().get(optionDay).getTasks().listIterator();
					while(liStudentTasks.hasNext()){
						Task studentTask = liStudentTasks.next();
						
						double tmpDistance = studentTask.getDistance(optionTask);
						if(tmpDistance < minDistance)
							minDistance = tmpDistance;
					}//end of student task iteration
					if(minDistance != 500)
						totalDistance = totalDistance + minDistance;
					
				}//end of option task iteration
				aveDistance = aveDistance + totalDistance;
				
			}//end of option days iteration
			option.setRank((int)((aveDistance/ctr)*100));
			
		}//end of option iteration
		
	}
	
	/**
	 * Sorts the options of the student according to their rank.
	 */
	public void sortOptionsByRank(){
		List<CourseOffering> retval = options;
		int n = retval.size();
		for(int i=0; i<n-1; i++){
			for(int j=0; j<(n-1-i); j++){
				CourseOffering a = retval.get(j);
				CourseOffering b = retval.get(j+1);
				if(compareOptionsByRank(a,b)>0){
					retval.set(j,b);
					retval.set(j+1,a);
				}
			}
		}
		options = retval;
	}

	/**
	 * Returns the list of options of the student.
	 * @return the list of options of the student
	 */
	public List<CourseOffering> getOptions() {
		return options;
	}
	
	/**
	 * Updates the schedule of the student. Adds the course offering's class to the schedule of the student.
	 * @param courseOffering
	 */
	public void updateSchedule(CourseOffering courseOffering){
		
		List<String> rawDays = new Vector<String>();
		
		/*
		 * lecture
		 */
		if(!courseOffering.getLectureDay().equals("TBA") && !courseOffering.getLectureDay().equals(null) && !courseOffering.getLectureDay().equals("")){
			rawDays = Day.tokenize(courseOffering.getLectureDay());
			for(String dayString : rawDays){
				Task task = Task.processRawData(courseOffering.getLectureTime());
				if(task!=null)
					schedule.getDays().get(Day.MAP_DAY.get(dayString)).addTasks(task);
			}
		}
		
		/*
		 * 	laboratory
		 */
		if(!courseOffering.getLaboratoryDay().equals("TBA") && !courseOffering.getLaboratoryDay().equals(null) && !courseOffering.getLaboratoryDay().equals("")){
			rawDays = Day.tokenize(courseOffering.getLaboratoryDay());
			for(String dayString : rawDays){
				Task task = Task.processRawData(courseOffering.getLaboratoryTime());
				if(task!=null)
					schedule.getDays().get(Day.MAP_DAY.get(dayString)).addTasks(task);
			}
		}
		
		/*
		 * 	recitation
		 */
		if(!courseOffering.getRecitationDay().equals("TBA") && !courseOffering.getRecitationDay().equals(null) && !courseOffering.getRecitationDay().equals("")){
			rawDays = Day.tokenize(courseOffering.getRecitationDay());
			for(String dayString : rawDays){
				Task task = Task.processRawData(courseOffering.getRecitationTime());
				String day = Day.MAP_DAY.get(dayString);
				if(task!=null)
					schedule.getDays().get(day).addTasks(task);
			}
		}
		
	}//end updateSchedule()

	/**
	 * Sets the list of options of the student.
	 * @param options
	 */
	public void setOptions(List<CourseOffering> options) {
		this.options = options;
	}
	
	/**
	 * Adds the course offering to the list of course offerings where the student is enlisted.
	 * @param courseOffering
	 */
	public void addEnlistedCourseOfferings(CourseOffering courseOffering){
		enlistedCourseOfferings.add(courseOffering);
	}
	
	/**
	 * Removes the given course to the list of recommended courses of the student.
	 * @param course
	 */
	public void removeRecommendedCourse(Course course){
		int index = 0;
		int ctr = 0;
		ListIterator<Course> li = recommendedCourses.listIterator();
		while(li.hasNext()){
			Course recommendedCourse = li.next();
			if(recommendedCourse.getCourseCode().equals(course.getCourseCode())){
				index = ctr;
				break;
			}
			ctr++;
		}
		recommendedCourses.remove(index);
	}
	
}
